home *** CD-ROM | disk | FTP | other *** search
/ MacHack 1993 / MacHack 1993.toast / MacHack™ 1987-1992 / MacHack™ '90 / Source Code ƒ / Misc. Pascal ƒ / Jay's Cookie / CookieEdit.p < prev    next >
Encoding:
Text File  |  1990-06-15  |  40.9 KB  |  1,390 lines  |  [TEXT/MPS ]

  1. {
  2.     CookieEdit.p -- A simple editor for my fortune files
  3.  
  4.     Programmer:
  5.         D. Jay Newman
  6.  
  7.     Date:
  8.         8/3/89
  9.  
  10.     Notes:
  11.         Skeliton based on the MPW Example TESample.p.  This is released into the
  12.         public domain (at least my code is).
  13. }
  14.  
  15. PROGRAM CookieEdit;
  16.  
  17.  
  18. {
  19.  
  20.     Edit File
  21.  
  22.         This program edits a file which consists of (1) a LONGINT which gives the
  23.         total number of fortune cookies, (2) a number of LONGINT offsets to the cookies,
  24.         equal to n + 1 (the last marks the byte after the end of the last cookie), and
  25.         (3) the text of the cookies themselves, with no dividing characters between them.
  26.         The offsets of the cookies in (2) start from the beginning of the cookie text (3);
  27.         that is, the first cookie begins at offset 0.
  28. }
  29.  
  30. {$D+}
  31.  
  32. USES
  33.     MemTypes, QuickDraw, OSIntf, ToolIntf, Traps;
  34.  
  35.  
  36. CONST
  37.  
  38.     kTextMargin                = 2;        {Number of pixels to leave around border}
  39.  
  40.     kMaxOpenDocuments        = 1;        {Maximum number of open documents}
  41.  
  42.     kMinDocDim                = 64;        {Minimum dimension of window for GrowWindow}
  43.  
  44.     kControlInvisible        = 0;        {Make control invisible}
  45.     kControlVisible            = $FF;        {Make control visible again}
  46.  
  47.     kScrollbarWidth            = 16;        {Width of scroll bar, in pixels}
  48.     kScrollbarAdjust        = kScrollbarWidth - 1;
  49.                                         {Used for adjusting things}
  50.  
  51.     kScrollTweek            = 2;        {Used to have borders coincide}
  52.  
  53.     kCRChar                    = 13;        {Carriage return}
  54.     kDelChar                = 8;        {Delete}
  55.  
  56.     kMaxTELength            = 32000;    {Max number of chars in TERec}
  57.  
  58.     kSysEnvironsVersion        = 1;        {Which version of SysEnvirons we understand}
  59.  
  60.   {    Event constants}
  61.     kOSEvent                = app4Evt;    {event used by MultiFinder}
  62.     kSuspendResumeMessage    = 1;        {high byte of suspend/resume event message}
  63.     kResumeMask                = 1;        {bit of message field for resume vs. suspend}
  64.     kMouseMovedMessage        = $FA;        {high byte of mouse-moved event message}
  65.  
  66.  
  67.     kMinHeap    = 29 * 1024;            {Minimum heap size to run in}
  68.     
  69.     kMinSpace    = 20 * 1024;            {Minimum space resulting from PurgeSpace}
  70.     
  71.   {    Used to set up wide open rectangles and regions}
  72.     kExtremeNeg        = -32768;
  73.     kExtremePos        = 32767 - 1;    {required for old region bug}
  74.  
  75.     kTESlop            = 1024;            {Extra security when pre-flighting edit commands}
  76.  
  77.     kErrStrings        = 128;            {Resource ID for error strings (STR#)}
  78.  
  79.   {    The following are indicies into STR# resources}
  80.     eWrongMachine    = 1;
  81.     eSmallSize        = 2;
  82.     eNoMemory        = 3;
  83.     eNoSpaceCut        = 4;
  84.     eNoCut            = 5;
  85.     eNoCopy            = 6;
  86.     eExceedPaste    = 7;
  87.     eNoSpacePaste    = 8;
  88.     eNoWindow        = 9;
  89.     eExceedChar        = 10;
  90.     eNoPaste        = 11;
  91.  
  92.   {    The following constants are all resource IDs}
  93.     rMenuBar        = 128;            {Application's menu bar}
  94.     rAboutAlert        = 128;            {About alert}
  95.     rUserAlert        = 129;            {User error alert}
  96.     rDocWindow        = 128;            {Application's window}
  97.     rHScroll        = 129;            {Horizontal scrollbar control}
  98.  
  99.   {    Menus and their items}
  100.     mApple        = 128;                {Apple menu}
  101.     iAbout        = 1;
  102.  
  103.     mFile        = 129;                {File menu}
  104.     iNew        = 1;
  105.     iOpen        = 2;
  106.     iClose        = 4;
  107.     iSave        = 5;
  108.     iSaveAs        = 6;
  109.     iQuit        = 12;
  110.  
  111.     mEdit        = 130;                {Edit menu}
  112.     iUndo        = 1;
  113.     iCut        = 3;
  114.     iCopy        = 4;
  115.     iPaste        = 5;
  116.     iClear        = 6;
  117.  
  118.     mFind        = 150;
  119.     iFind        = 1;
  120.     iFindSame    = 2;
  121.     iFindSel    = 3;
  122.     iDisplaySel    = 4;
  123.     iReplace    = 6;
  124.     iReplSame    = 7;
  125.     iGotoTop    = 9;
  126.     iGotoBottom    = 10;
  127.     iGotoLine    = 11;
  128.  
  129.     mFortunes    = 151;
  130.     iNewFortune    = 1;
  131.     iDelFortune    = 2;
  132.  
  133.  
  134. TYPE
  135.  
  136.     CookieOffsetArray    =    ARRAY [0..7999] OF Ptr;
  137.  
  138.     DocumentRecord        = RECORD
  139.         docWindow:        WindowRecord;
  140.         docTE:            TEHandle;
  141.         docVScroll:        ControlHandle;            {Vertical scrollbar}
  142.         docClik:        ProcPtr;
  143.         currentCookie:    INTEGER;                {The number of the current cookie}
  144.         numCookies:        INTEGER;                {Total number of cookies in file}
  145.         cookOffsets:    ^^CookieOffsetHandle;    {Pointer to array of pointers}
  146.         cookieText:        Handle;                    
  147.     END;
  148.  
  149.     DocumentPeek    = ^DocumentRecord;
  150.  
  151.  
  152. VAR
  153.  
  154.     gMac                : SysEnvRec;    {Holds the result of a SysEnvirons call}
  155.  
  156.     gHasWaitNextEvent    : BOOLEAN;        {Obvious}
  157.  
  158.     gInBackground        : BOOLEAN;        {Maintained by Initialize and DoEvent}
  159.  
  160.     gNumDocuments        : INTEGER;        {Maintained by Initialize, DoNew, and DoCloseWindow}
  161.  
  162.     
  163. {$S Initialize}
  164. FUNCTION TrapAvailable(tNumber: INTEGER; tType: TrapType): BOOLEAN;
  165.  
  166. {Check to see if a given trap is implemented. This is only used by the
  167.  Initialize routine in this program, so we put it in the Initialize segment.}
  168.  
  169. BEGIN
  170.     TrapAvailable := NGetTrapAddress(tNumber, tType) <> GetTrapAddress(_Unimplemented);
  171. END; {TrapAvailable}
  172.  
  173.  
  174. {$S Main}
  175. FUNCTION IsDAWindow(window: WindowPtr): BOOLEAN;
  176.  
  177. {Check if a window belongs to a desk accessory.}
  178.  
  179. BEGIN
  180.     IF window = NIL THEN
  181.         IsDAWindow := FALSE
  182.     ELSE    {DA windows have negative windowKinds}
  183.         IsDAWindow := WindowPeek(window)^.windowKind < 0;
  184. END; {IsDAWindow}
  185.  
  186.  
  187. {Check if a window belongs to the application.}
  188.  
  189. {$S Main}
  190. FUNCTION IsAppWindow(window: WindowPtr): BOOLEAN;
  191. BEGIN
  192.     IF window = NIL THEN
  193.         IsAppWindow := FALSE
  194.     ELSE    {application windows have non-negative windowKinds}
  195.         IsAppWindow := WindowPeek(window)^.windowKind >= 0;
  196. END; {IsAppWindow}
  197.  
  198.  
  199. {Display an alert that tells the user an error occurred, then exit the program.
  200.  This routine is used as an ultimate bail-out for serious errors that prohibit
  201.  the continuation of the application. Errors that do not require the termination
  202.  of the application should be handled in a different manner. Error checking and
  203.  reporting has a place even in the simplest application. The error number is used
  204.  to index an 'STR#' resource so that a relevant message can be displayed.}
  205.  
  206. {$S Main}
  207. PROCEDURE AlertUser(error: INTEGER);
  208. VAR
  209.     itemHit    : INTEGER;
  210.     message    : Str255;
  211. BEGIN
  212.     SetCursor(arrow);
  213.     GetIndString(message, kErrStrings, error);
  214.     ParamText(message, '', '', '');
  215.     itemHit := Alert(rUserAlert, NIL);
  216. END; {AlertUser}
  217.  
  218.  
  219.  
  220. {return a rectangle that is inset from the portRect by the size of
  221. the scrollbars and a little extra margin.}
  222.  
  223. {$S Main}
  224. PROCEDURE GetTERect(window: WindowPtr; VAR teRect: Rect);
  225. BEGIN
  226.     teRect := window^.portRect;
  227.     InsetRect(teRect, kTextMargin, kTextMargin);            {adjust for margin}
  228.     teRect.bottom := teRect.bottom - kScrollbarAdjust;    {and for the scrollbars}
  229.     teRect.right := teRect.right - kScrollbarAdjust;
  230. END; {GetTERect}
  231.  
  232.  
  233.  
  234. {Close a window. This handles desk accessory and application windows.}
  235.  
  236. { 1.01 - At this point, if there was a document associated with a
  237.  window, you could do any document saving processing if it is 'dirty'.
  238.  DoCloseWindow would return TRUE if the window actually closed, i.e.,
  239.  the user didn’t cancel from a save dialog. This result is handy when
  240.  the user quits an application, but then cancels the save of a document
  241.  associated with a window.}
  242.  
  243. {$S Main}
  244. FUNCTION DoCloseWindow(window: WindowPtr) : BOOLEAN;
  245. BEGIN
  246.     DoCloseWindow := TRUE;
  247.     IF IsDAWindow(window) THEN
  248.         CloseDeskAcc(WindowPeek(window)^.windowKind)
  249.     ELSE IF IsAppWindow(window) THEN BEGIN
  250.         WITH DocumentPeek(window)^ DO
  251.             IF docTE <> NIL THEN
  252.                 TEDispose(docTE);        {dispose the TEHandle}
  253.         DisposeWindow(window);
  254.         gNumDocuments := gNumDocuments - 1;
  255.     END;
  256. END; {DoCloseWindow}
  257.  
  258.  
  259. {$S Main}
  260. PROCEDURE AdjustViewRect(docTE: TEHandle);
  261.  
  262. {Update the TERec's view rect so that it is the greatest multiple of
  263. the lineHeight and still fits in the old viewRect.}
  264.  
  265. BEGIN
  266.     WITH docTE^^ DO BEGIN
  267.         viewRect.bottom := (((viewRect.bottom - viewRect.top) DIV lineHeight) *
  268.                             lineHeight) + viewRect.top;
  269.     END;
  270. END; {AdjustViewRect}
  271.  
  272.  
  273. {$S Main}
  274. PROCEDURE AdjustTE(window: WindowPtr);
  275.  
  276. {Scroll the TERec around to match up to the potentially updated scrollbar
  277. values. This is really useful when the window resizes such that the
  278. scrollbars become inactive and the TERec had been previously scrolled.}
  279.  
  280. VAR
  281.     value    : INTEGER;
  282. BEGIN
  283.     WITH DocumentPeek(window)^ DO BEGIN
  284.         TEScroll((docTE^^.viewRect.left - docTE^^.destRect.left) - GetCtlValue(docHScroll),
  285.                 (docTE^^.viewRect.top - docTE^^.destRect.top) -
  286.                     (GetCtlValue(docVScroll) * docTE^^.lineHeight),
  287.                 docTE);
  288.     END;
  289. END; {AdjustTE}
  290.  
  291.  
  292. {$S Main}
  293. PROCEDURE AdjustHV(isVert: BOOLEAN; control: ControlHandle; docTE: TEHandle; canRedraw: BOOLEAN);
  294.  
  295. {Calculate the new control maximum value and current value, whether it is the horizontal or
  296. vertical scrollbar. The vertical max is calculated by comparing the number of lines to the
  297. vertical size of the viewRect. The horizontal max is calculated by comparing the maximum document
  298. width to the width of the viewRect. The current values are set by comparing the offset between
  299. the view and destination rects. If necessary and we canRedraw, have the control be re-drawn by
  300. calling ShowControl.}
  301.  
  302. VAR
  303.     value, lines, max    : INTEGER;
  304.     oldValue, oldMax    : INTEGER;
  305. BEGIN
  306.     oldValue := GetCtlValue(control);
  307.     oldMax := GetCtlMax(control);
  308.     IF isVert THEN BEGIN
  309.         lines := docTE^^.nLines;
  310.         {since nLines isn’t right if the last character is a return, check for that case}
  311.         IF Ptr(ORD(docTE^^.hText^) + docTE^^.teLength - 1)^ = kCRChar THEN
  312.             lines := lines + 1;
  313.         max := lines - ((docTE^^.viewRect.bottom - docTE^^.viewRect.top) DIV docTE^^.lineHeight);
  314.     END ELSE
  315.         max := kMaxDocWidth - (docTE^^.viewRect.right - docTE^^.viewRect.left);
  316.     IF max < 0 THEN max := 0;            {check for negative values}
  317.     SetCtlMax(control, max);
  318.     IF isVert THEN
  319.         value := (docTE^^.viewRect.top - docTE^^.destRect.top) DIV docTE^^.lineHeight
  320.     ELSE
  321.         value := docTE^^.viewRect.left - docTE^^.destRect.left;
  322.     IF value < 0 THEN
  323.         value := 0
  324.     ELSE IF value > max THEN
  325.         value := max;                    {pin the value to within range}
  326.     SetCtlValue(control, value);
  327.     IF canRedraw & ( (max <> oldMax) | (value <> oldValue) ) THEN
  328.         ShowControl(control);            {check to see if the control can be re-drawn}
  329. END; {AdjustHV}
  330.  
  331.  
  332. {$S Main}
  333. PROCEDURE AdjustScrollValues(window: WindowPtr; canRedraw: BOOLEAN);
  334.  
  335. {Simply call the common adjust routine for the vertical and horizontal scrollbars.}
  336.  
  337. BEGIN
  338.     WITH DocumentPeek(window)^ DO BEGIN
  339.         AdjustHV(TRUE, docVScroll, docTE, canRedraw);
  340.         AdjustHV(FALSE, docHScroll, docTE, canRedraw);
  341.     END;
  342. END; {AdjustScrollValues}
  343.  
  344.  
  345. {$S Main}
  346. PROCEDURE AdjustScrollSizes(window: WindowPtr);
  347.  
  348. {Re-calculate the position and size of the viewRect and the scrollbars.
  349.  kScrollTweek compensates for off-by-one requirements of the scrollbars
  350.  to have borders coincide with the growbox.}
  351.  
  352. VAR
  353.     teRect    : Rect;
  354. BEGIN
  355.     GetTERect(window, teRect);                                        {start with teRect}
  356.     WITH DocumentPeek(window)^, window^.portRect DO BEGIN
  357.         docTE^^.viewRect := teRect;
  358.         AdjustViewRect(docTE);                                        {snap to nearest line}
  359.         MoveControl(docVScroll, right - kScrollbarAdjust, -1);
  360.         SizeControl(docVScroll, kScrollbarWidth, (bottom - top) -
  361.                         (kScrollbarAdjust - kScrollTweek));
  362.         MoveControl(docHScroll, -1, bottom - kScrollbarAdjust);
  363.         SizeControl(docHScroll, (right - left) - (kScrollbarAdjust -
  364.                         kScrollTweek), kScrollbarWidth);
  365.     END;
  366. END; {AdjustScrollSizes}
  367.  
  368.  
  369. {$S Main}
  370. PROCEDURE AdjustScrollbars(window: WindowPtr; needsResize: BOOLEAN);
  371.  
  372. {Turn off the controls by jamming a zero into their contrlVis fields (HideControl erases them
  373. and we don't want that). If the controls are to be resized as well, call the procedure to do that,
  374. then call the procedure to adjust the maximum and current values. Finally re-enable the controls
  375. by jamming a $FF in their contrlVis fields.}
  376.  
  377. VAR
  378.     oldMax, oldVal    : INTEGER;
  379. BEGIN
  380.     WITH DocumentPeek(window)^ DO BEGIN
  381.         {First, turn visibility of scrollbars off so we won’t get unwanted redrawing}
  382.         docVScroll^^.contrlVis := kControlInvisible;    {turn them off}
  383.         docHScroll^^.contrlVis := kControlInvisible;
  384.         IF needsResize THEN                                {move and size if needed}
  385.             AdjustScrollSizes(window);
  386.         AdjustScrollValues(window, NOT needsResize);    {fool with max and current value}
  387.         {Now, restore visibility in case we never had to ShowControl during adjustment}
  388.         docVScroll^^.contrlVis := kControlVisible;        {turn them on}
  389.         docHScroll^^.contrlVis := kControlVisible;
  390.     END;
  391. END; {AdjustScrollbars}
  392.  
  393.  
  394. {$S Main}
  395. {$PUSH} {$Z+}
  396. PROCEDURE PascalClikLoop;
  397.  
  398. {Gets called from our assembly language routine, AsmClikLoop, which is in
  399.  turn called by the TEClick toolbox routine. Saves the windows clip region,
  400.  sets it to the portRect, adjusts the scrollbar values to match the TE scroll
  401.  amount, then restores the clip region.}
  402.  
  403. VAR
  404.     window    : WindowPtr;
  405.     region    : RgnHandle;
  406. BEGIN
  407.     window := FrontWindow;
  408.     region := NewRgn;
  409.     GetClip(region);                    {save the old clip}
  410.     ClipRect(window^.portRect);            {set the new clip}
  411.     AdjustScrollValues(window, TRUE);    {pass TRUE for canRedraw}
  412.     SetClip(region);                    {restore the old clip}
  413.     DisposeRgn(region);
  414. END; {PascalClikLoop}
  415. {$POP}
  416.  
  417.  
  418. {$S Main}
  419. {$PUSH} {$Z+}
  420. FUNCTION GetOldClikLoop : ProcPtr;
  421.  
  422. {Gets called from our assembly language routine, AsmClikLoop, which is in
  423. turn called by the TEClick toolbox routine. It returns the address of the
  424. default clikLoop routine that was put into the TERec by TEAutoView to
  425. AsmClikLoop so that it can call it.}
  426.  
  427. BEGIN
  428.     GetOldClikLoop := DocumentPeek(FrontWindow)^.docClik;
  429. END; {GetOldClikLoop}
  430. {$POP}
  431.  
  432.  
  433. PROCEDURE AsmClikLoop; EXTERNAL;
  434.  
  435. {A reference to our assembly language routine that gets attached to the clikLoop
  436. field of our TE record.}
  437.  
  438.  
  439. {$S Main}
  440. PROCEDURE DoNew;
  441.  
  442. {Create a new document and window.}
  443.  
  444. VAR
  445.     good, ignore        : BOOLEAN;
  446.     storage                : Ptr;
  447.     window                : WindowPtr;
  448.     destRect, viewRect    : Rect;
  449.  
  450. BEGIN
  451.     storage := NewPtr(SIZEOF(DocumentRecord));
  452.     IF storage <> NIL THEN BEGIN
  453.         window := GetNewWindow(rDocWindow, storage, WindowPtr(-1));
  454.         IF window <> NIL THEN BEGIN
  455.             gNumDocuments := gNumDocuments + 1;    {this will be decremented when we call DoCloseWindow}
  456.             good := FALSE;
  457.             SetPort(window);
  458.             WITH window^, DocumentPeek(window)^ DO BEGIN
  459.                 GetTERect(window, viewRect);
  460.                 destRect := viewRect;
  461.                 destRect.right := destRect.left + kMaxDocWidth;
  462.                 docTE := TENew(destRect, viewRect);
  463.                 AdjustViewRect(docTE);
  464.                 TEAutoView(TRUE, docTE);
  465.                 docClik := docTE^^.clikLoop;
  466.                 docTE^^.clikLoop := @AsmClikLoop;
  467.                 IF docTE <> NIL THEN
  468.                     good := TRUE;                {if TENew succeeded, we have a good document}
  469.                 IF good THEN BEGIN
  470.                     docVScroll := GetNewControl(rVScroll, window);
  471.                     good := (docVScroll <> NIL);
  472.                 END;
  473.                 IF good THEN BEGIN
  474.                     docHScroll := GetNewControl(rHScroll, window);
  475.                     good := (docHScroll <> NIL);
  476.                 END;
  477.                 IF good THEN BEGIN
  478.                     {FALSE to AdjustScrollValues means musn’t redraw; technically, of course,
  479.                     the window is hidden so it wouldn’t matter whether we called ShowControl or not.}
  480.                     AdjustScrollValues(window, FALSE);
  481.                     ShowWindow(window);            {if the document is good, make the window visible}
  482.                 END ELSE BEGIN
  483.                     ignore := DoCloseWindow(window);    {otherwise regret we ever created it...}
  484.                     AlertUser(eNoWindow);                {and tell user}
  485.                 END
  486.             END;
  487.         END ELSE
  488.             DisposPtr(storage);                    {get rid of the storage if it is never used}
  489.     END;
  490. END; {DoNew}
  491.  
  492.  
  493. {$S Main}
  494. PROCEDURE BigBadError(error: INTEGER);
  495. BEGIN
  496.     AlertUser(error);
  497.     ExitToShell;
  498. END;
  499.  
  500.  
  501. {$S Initialize}
  502. PROCEDURE Initialize;
  503.  
  504. {Set up the whole world, including global variables, Toolbox managers,
  505.  menus, and a single blank document.}
  506.  
  507. {1.01 - The code that used to be part of ForceEnvirons has been moved into
  508.  this module. If an error is detected, instead of merely doing an ExitToShell,
  509.  which leaves the user without much to go on, we call AlertUser, which puts
  510.  up a simple alert that just says an error occurred and then calls ExitToShell.
  511.  Since there is no other cleanup needed at this point if an error is detected,
  512.  this form of error- handling is acceptable. If more sophisticated error recovery
  513.  is needed, an exception mechanism, such as is provided by Signals, can be used.}
  514.  
  515. VAR
  516.     menuBar                : Handle;
  517.     total, contig        : LongInt;
  518.     ignoreResult        : BOOLEAN;
  519.     event                : EventRecord;
  520.     count, ignoreError    : INTEGER;
  521.     
  522.     PROCEDURE BigBadError(error: INTEGER);
  523.     BEGIN
  524.         AlertUser(error);
  525.         ExitToShell;
  526.     END;
  527.  
  528. BEGIN
  529.     gHasWaitNextEvent := TrapAvailable(_WaitNextEvent, ToolTrap);
  530.     gInBackground := FALSE;
  531.  
  532.     InitGraf(@thePort);
  533.     InitFonts;
  534.     InitWindows;
  535.     InitMenus;
  536.     TEInit;
  537.     InitDialogs(NIL);
  538.     InitCursor;
  539.  
  540.     {Call MPPOpen and ATPLoad at this point to initialize AppleTalk,
  541.      if you are using it.}
  542.     {NOTE -- It is no longer necessary, and actually unhealthy, to check
  543.      PortBUse and SPConfig before opening AppleTalk. The drivers are capable
  544.      of checking for port availability themselves.}
  545.     
  546.     {Now this next bit of code may make you toss your cookies, but it is
  547.      necessary to allow the default button of our alert be outlined.}
  548.      
  549.     FOR count := 1 TO 3 DO
  550.         ignoreResult := GetNextEvent(everyEvent, event);
  551.     
  552.     {Ignore the error returned from SysEnvirons; even if an error occurred,
  553.      the SysEnvirons glue will fill in the SysEnvRec. You can save a redundant
  554.      call to SysEnvirons by calling it after initializing AppleTalk.}
  555.      
  556.     ignoreError := SysEnvirons(kSysEnvironsVersion, gMac);
  557.     
  558.     {Make sure that the machine has at least 128K ROMs. If it doesn't, exit.}
  559.     
  560.     IF gMac.machineType < 0 THEN BigBadError(eWrongMachine);
  561.     
  562.     {1.01 - We used to make a check for memory at this point by examining ApplLimit,
  563.      ApplicZone, and StackSpace and comparing that to the minimum size we told
  564.      MultiFinder we needed. This did not work well because it assumed too much about
  565.      the relationship between what we asked MultiFinder for and what we would actually
  566.      get back, as well as how to measure it. Instead, we will use an alternate
  567.      method comprised of two steps.}
  568.      
  569.     {It is better to first check the size of the application heap against a value
  570.      that you have determined is the smallest heap the application can reasonably
  571.      work in. This number should be derived by examining the size of the heap that
  572.      is actually provided by MultiFinder when the minimum size requested is used.
  573.      The derivation of the minimum size requested from MultiFinder is described
  574.      in Sample.h. The check should be made because the preferred size can end up
  575.      being set smaller than the minimum size by the user. This extra check acts to
  576.      insure that your application is starting from a solid memory foundation.}
  577.      
  578.     IF ORD(GetApplLimit) - ORD(ApplicZone) < kMinHeap THEN BigBadError(eSmallSize);
  579.     
  580.     {Next, make sure that enough memory is free for your application to run. It
  581.      is possible for a situation to arise where the heap may have been of required
  582.      size, but a large scrap was loaded which left too little memory. To check for
  583.      this, call PurgeSpace and compare the result with a value that you have determined
  584.      is the minimum amount of free memory your application needs at initialization.
  585.      This number can be derived several different ways. One way that is fairly
  586.      straightforward is to run the application in the minimum size configuration
  587.      as described previously. Call PurgeSpace at initialization and examine the value
  588.      returned. However, you should make sure that this result is not being modified
  589.      by the scrap's presence. You can do that by calling ZeroScrap before calling
  590.      PurgeSpace. Make sure to remove that call before shipping, though.}
  591.      
  592.     {* ZeroScrap; *}
  593.     PurgeSpace(total, contig);
  594.     IF total < kMinSpace THEN
  595.         IF UnloadScrap <> noErr THEN
  596.             BigBadError(eNoMemory)
  597.         ELSE BEGIN
  598.             PurgeSpace(total, contig);
  599.             IF total < kMinSpace THEN
  600.                 BigBadError(eNoMemory);
  601.         END;
  602.  
  603.     {The extra benefit to waiting until after the Toolbox Managers have been initialized
  604.      to check memory is that we can now give the user an alert to tell her/him what
  605.      happened. Although it is possible that the memory situation could be worsened by
  606.      displaying an alert, MultiFinder would gracefully exit the application with
  607.      an informative alert if memory became critical. Here we are acting more
  608.      in a preventative manner to avoid future disaster from low-memory problems.}
  609.  
  610.     menuBar := GetNewMBar(rMenuBar);        {read menus into menu bar}
  611.     IF menuBar = NIL THEN
  612.         BigBadError(eNoMemory);
  613.     SetMenuBar(menuBar);                    {install menus}
  614.     DisposHandle(menuBar);
  615.     AddResMenu(GetMHandle(mApple), 'DRVR');    {add DA names to Apple menu}
  616.     DrawMenuBar;
  617.  
  618.     gNumDocuments := 0;
  619.  
  620.     {do other initialization here}
  621.  
  622.     DoNew;                                    {create a single empty document}
  623. END; {Initialize}
  624.  
  625.  
  626. (**************************************************************************************
  627. 1.01 - PROCEDURE DoCloseBehind(window: WindowPtr); was removed.
  628.  
  629. {1.01 - DoCloseBehind was a good idea for closing windows when quitting
  630.  and not having to worry about updating the windows, but it suffered
  631.  from a fatal flaw. If a desk accessory owned two windows, it would
  632.  close both those windows when CloseDeskAcc was called. When DoCloseBehind
  633.  got around to calling DoCloseWindow for that other window that was already
  634.  closed, things would go very poorly. Another option would be to have a
  635.  procedure, GetRearWindow, that would go through the window list and return
  636.  the last window. Instead, we decided to present the standard approach
  637.  of getting and closing FrontWindow until FrontWindow returns NIL. This
  638.  has a potential benefit in that the window whose document needs to be saved
  639.  may be visible since it is the front window, therefore decreasing the
  640.  chance of user confusion. For aesthetic reasons, the windows in the
  641.  application should be checked for updates periodically and have the
  642.  updates serviced.}
  643. **************************************************************************************)
  644.  
  645.  
  646. {$S Main}
  647. PROCEDURE Terminate;
  648.  
  649. {Clean up the application and exit. We close all of the windows so that
  650.  they can update their documents, if any.}
  651.  
  652. {1.01 - If we find out that a cancel has occurred, we won't exit to the
  653.  shell, but will return instead.}
  654.  
  655. VAR
  656.     aWindow    : WindowPtr;
  657.     closed    : BOOLEAN;
  658.  
  659. BEGIN
  660.     closed := TRUE;
  661.     REPEAT
  662.         aWindow := FrontWindow;                    {get the current front window}
  663.         IF aWindow <> NIL THEN
  664.             closed := DoCloseWindow(aWindow);    {close this window}
  665.     UNTIL (NOT closed) | (aWindow = NIL);        {do all windows}
  666.     IF closed THEN
  667.         ExitToShell;                            {exit if no cancellation}
  668. END; {Terminate}
  669.  
  670.  
  671. {$S Main}
  672. PROCEDURE AdjustMenus;
  673.  
  674. {Enable and disable menus based on the current state.
  675.  The user can only select enabled menu items. We set up all the menu items
  676.  before calling MenuSelect or MenuKey, since these are the only times that
  677.  a menu item can be selected. Note that MenuSelect is also the only time
  678.  the user will see menu items. This approach to deciding what enable/
  679.  disable state a menu item has the advantage of concentrating all the decision-
  680.  making in one routine, as opposed to being spread throughout the application.
  681.  Other application designs may take a different approach that may or may not be
  682.  as valid.}
  683.  
  684. VAR
  685.     window            : WindowPtr;
  686.     menu            : MenuHandle;
  687.     offset            : LONGINT;
  688.     undo            : BOOLEAN;
  689.     cutCopyClear    : BOOLEAN;
  690.     paste            : BOOLEAN;
  691.  
  692. BEGIN
  693.     window := FrontWindow;
  694.  
  695.     menu := GetMHandle(mFile);
  696.     IF gNumDocuments < kMaxOpenDocuments THEN
  697.         EnableItem(menu, iNew)        {New is enabled when we can open more documents}
  698.     ELSE
  699.         DisableItem(menu, iNew);
  700.     IF window <> NIL THEN            {Close is enabled when there is a window to close}
  701.         EnableItem(menu, iClose)
  702.     ELSE
  703.         DisableItem(menu, iClose);
  704.  
  705.     menu := GetMHandle(mEdit);
  706.     undo := FALSE;
  707.     cutCopyClear := FALSE;
  708.     paste := FALSE;
  709.     IF IsDAWindow(window) THEN BEGIN
  710.         undo := TRUE;                {all editing is enabled for DA windows}
  711.         cutCopyClear := TRUE;
  712.         paste := TRUE;
  713.     END ELSE IF IsAppWindow(window) THEN BEGIN
  714.         WITH DocumentPeek(window)^.docTE^^ DO
  715.             IF selStart < selEnd THEN
  716.                 cutCopyClear := TRUE;
  717.                 {Cut, Copy, and Clear is enabled for app. windows with selections}
  718.         IF GetScrap(NIL, 'TEXT', offset) > 0 THEN
  719.             paste := TRUE;            {Paste is enabled for app. windows}
  720.     END;
  721.     IF undo THEN
  722.         EnableItem(menu, iUndo)
  723.     ELSE
  724.         DisableItem(menu, iUndo);
  725.     IF cutCopyClear THEN BEGIN
  726.         EnableItem(menu, iCut);
  727.         EnableItem(menu, iCopy);
  728.         EnableItem(menu, iClear);
  729.     END ELSE BEGIN
  730.         DisableItem(menu, iCut);
  731.         DisableItem(menu, iCopy);
  732.         DisableItem(menu, iClear);
  733.     END;
  734.     IF paste THEN
  735.         EnableItem(menu, iPaste)
  736.     ELSE
  737.         DisableItem(menu, iPaste);
  738. END; {AdjustMenus}
  739.  
  740.  
  741. {$S Main}
  742. PROCEDURE DoMenuCommand(menuResult: LONGINT);
  743.  
  744. {This is called when an item is chosen from the menu bar (after calling
  745.  MenuSelect or MenuKey). It does the right thing for each command.}
  746.  
  747. VAR
  748.     menuID, menuItem        : INTEGER;
  749.     itemHit, daRefNum        : INTEGER;
  750.     daName                    : Str255;
  751.     ignoreResult, saveErr    : OSErr;
  752.     handledByDA                : BOOLEAN;
  753.     te                        : TEHandle;
  754.     window                    : WindowPtr;
  755.     ignore                    : BOOLEAN;
  756.     aHandle                    : Handle;
  757.     oldSize, newSize        : LongInt;
  758.     total, contig            : LongInt;
  759.  
  760. BEGIN
  761.     window := FrontWindow;
  762.     menuID := HiWrd(menuResult);    {use built-ins (for efficiency)...}
  763.     menuItem := LoWrd(menuResult);    {to get menu item number and menu number}
  764.     CASE menuID OF
  765.     
  766.         mApple:
  767.             CASE menuItem OF
  768.                 iAbout:                {bring up alert for About}
  769.                     itemHit := Alert(rAboutAlert, NIL);
  770.                 OTHERWISE BEGIN        {all non-About items in this menu are DAs}
  771.                     GetItem(GetMHandle(mApple), menuItem, daName);
  772.                     daRefNum := OpenDeskAcc(daName);
  773.                 END;
  774.             END;
  775.             
  776.         mFile:
  777.             CASE menuItem OF
  778.                 iNew:
  779.                     DoNew;
  780.                 iClose:
  781.                     ignore := DoCloseWindow(window); {we don't care if cancelled}
  782.                 iQuit:
  783.                     Terminate;
  784.             END;
  785.             
  786.         mEdit: BEGIN                {call SystemEdit for DA editing & MultiFinder}
  787.             IF NOT SystemEdit(menuItem-1) THEN BEGIN
  788.                 te := DocumentPeek(window)^.docTE;
  789.                 CASE menuItem OF
  790.                 
  791.                     iCut: BEGIN        {after cutting, export the TE scrap}
  792.                         IF ZeroScrap = noErr THEN BEGIN
  793.                             PurgeSpace(total, contig);
  794.                             IF (te^^.selEnd - te^^.selStart) + kTESlop > contig THEN
  795.                                 AlertUser(eNoSpaceCut)
  796.                             ELSE BEGIN
  797.                                 TECut(te);
  798.                                 IF TEToScrap <> noErr THEN BEGIN
  799.                                     AlertUser(eNoCut);
  800.                                     ignoreResult := ZeroScrap;
  801.                                 END;
  802.                             END;
  803.                         END;
  804.                     END;
  805.                     
  806.                     iCopy: BEGIN    {after copying, export the TE scrap}
  807.                         IF ZeroScrap = noErr THEN BEGIN
  808.                             TECopy(te);
  809.                             IF TEToScrap <> noErr THEN BEGIN
  810.                                 AlertUser(eNoCopy);
  811.                                 ignoreResult := ZeroScrap;
  812.                             END;
  813.                         END;
  814.                     END;
  815.                     
  816.                     iPaste: BEGIN    {import the TE scrap before pasting}
  817.                         IF TEFromScrap = noErr THEN BEGIN
  818.                             IF TEGetScrapLen + (te^^.teLength -
  819.                                 (te^^.selEnd - te^^.selStart)) > kMaxTELength THEN
  820.                                 AlertUser(eExceedPaste)
  821.                             ELSE BEGIN
  822.                                     aHandle := Handle(TEGetText(te));
  823.                                     oldSize := GetHandleSize(aHandle);
  824.                                     newSize := oldSize + TEGetScrapLen + kTESlop;
  825.                                     SetHandleSize(aHandle, newSize);
  826.                                     saveErr := MemError;
  827.                                     SetHandleSize(aHandle, oldSize);
  828.                                     IF saveErr <> noErr THEN
  829.                                         AlertUser(eNoSpacePaste)
  830.                                     ELSE
  831.                                         TEPaste(te);
  832.                                 END;
  833.                         END ELSE
  834.                             AlertUser(eNoPaste);
  835.                     END;
  836.                     
  837.                     iClear:
  838.                         TEDelete(te);
  839.                         
  840.                 END;
  841.                 AdjustScrollbars(window, FALSE);
  842.                 AdjustTE(window);
  843.             END;
  844.         END;
  845.     END;
  846.     HiliteMenu(0);                    {unhighlight what MenuSelect (or MenuKey) hilited}
  847. END; {DoMenuCommand}
  848.  
  849.  
  850. {$S Main}
  851. PROCEDURE DrawWindow(window: WindowPtr);
  852.  
  853. {Draw the contents of an application window.}
  854.  
  855. BEGIN
  856.     SetPort(window);
  857.     WITH window^ DO BEGIN
  858.         EraseRect(portRect);        {as per TextEdit chapter of Inside Macintosh}
  859.         DrawControls(window);        {this ordering makes for a better appearance}
  860.         DrawGrowIcon(window);
  861.         TEUpdate(portRect, DocumentPeek(window)^.docTE);
  862.     END;
  863. END; {DrawWindow}
  864.  
  865.  
  866. {$S Main}
  867. FUNCTION GetSleep: LONGINT;
  868.  
  869. {Calculate a sleep value for WaitNextEvent. This takes into account the things
  870.  that DoIdle does with idle time.}
  871.  
  872. VAR
  873.     sleep    : LONGINT;
  874.     window    : WindowPtr;
  875.  
  876. BEGIN
  877.     sleep := MAXLONGINT;                    {default value for sleep}
  878.     IF NOT gInBackground THEN BEGIN            {if we are in front...}
  879.         window := FrontWindow;            {and the front window is ours...}
  880.         IF IsAppWindow(window) THEN BEGIN
  881.             WITH DocumentPeek(window)^.docTE^^ DO
  882.                 IF selStart = selEnd THEN    {and the selection is an insertion point...}
  883.                     sleep := GetCaretTime;    {we need to blink the insertion point}
  884.         END;
  885.     END;
  886.     GetSleep := sleep;
  887. END; {GetSleep}
  888.  
  889.  
  890. {$S Main}
  891. PROCEDURE CommonAction(control: ControlHandle; VAR amount: INTEGER);
  892.  
  893. {Common algorithm for setting the new value of a control. It returns the actual amount
  894. the value of the control changed. Note the pinning is done for the sake of returning
  895. the amount the control value changed.}
  896.  
  897. VAR
  898.     value, max    : INTEGER;
  899.     window        : WindowPtr;
  900. BEGIN
  901.     value := GetCtlValue(control);    {get current value}
  902.     max := GetCtlMax(control);        {and max value}
  903.     amount := value - amount;
  904.     IF amount < 0 THEN
  905.         amount := 0
  906.     ELSE IF amount > max THEN
  907.         amount := max;
  908.     SetCtlValue(control, amount);
  909.     amount := value - amount;        {calculate true change}
  910. END; {CommonAction}
  911.  
  912.  
  913. {$S Main}
  914. PROCEDURE VActionProc(control: ControlHandle; part: INTEGER);
  915.  
  916. {Determines how much to change the value of the vertical scrollbar by and how
  917. much to scroll the TE record.}
  918.  
  919. VAR
  920.     amount    : INTEGER;
  921.     window    : WindowPtr;
  922. BEGIN
  923.     IF part <> 0 THEN BEGIN
  924.         window := control^^.contrlOwner;
  925.         WITH DocumentPeek(window)^, DocumentPeek(window)^.docTE^^ DO BEGIN
  926.             CASE part OF
  927.                 inUpButton, inDownButton:
  928.                     amount := 1;                                                {one line}
  929.                 inPageUp, inPageDown:
  930.                     amount := (viewRect.bottom - viewRect.top) DIV lineHeight;    {one page}
  931.             END;
  932.             IF (part = inDownButton) | (part = inPageDown) THEN
  933.                 amount := -amount;                                                {reverse direction}
  934.             CommonAction(control, amount);
  935.             IF amount <> 0 THEN
  936.                 TEScroll(0, amount * docTE^^.lineHeight, docTE);
  937.         END;
  938.     END;
  939. END; {VActionProc}
  940.  
  941.  
  942. {$S Main}
  943. PROCEDURE HActionProc(control: ControlHandle; part: INTEGER);
  944.  
  945. {Determines how much to change the value of the horizontal scrollbar by and how
  946. much to scroll the TE record.}
  947.  
  948. VAR
  949.     amount    : INTEGER;
  950.     window    : WindowPtr;
  951. BEGIN
  952.     IF part <> 0 THEN BEGIN
  953.         window := control^^.contrlOwner;
  954.         WITH DocumentPeek(window)^, DocumentPeek(window)^.docTE^^ DO BEGIN
  955.             CASE part OF
  956.                 inUpButton, inDownButton:                                        {a few pixels}
  957.                     amount := kButtonScroll;
  958.                 inPageUp, inPageDown:
  959.                     amount := viewRect.right - viewRect.left;                    {a page}
  960.             END;
  961.             IF (part = inDownButton) | (part = inPageDown) THEN
  962.                 amount := -amount;                                                {reverse direction}
  963.             CommonAction(control, amount);
  964.             IF amount <> 0 THEN
  965.                 TEScroll(amount, 0, docTE);
  966.         END;
  967.     END;
  968. END; {HActionProc}
  969.  
  970.  
  971. {$S Main}
  972. PROCEDURE DoIdle;
  973.  
  974. {This is called whenever we get an null event or a mouse-moved event.
  975.  It takes care of necessary periodic actions. For this program, it calls TEIdle.}
  976.  
  977. VAR
  978.     window    : WindowPtr;
  979.  
  980. BEGIN
  981.     window := FrontWindow;
  982.     IF IsAppWindow(window) THEN
  983.         TEIdle(DocumentPeek(window)^.docTE);
  984. END; {DoIdle}
  985.  
  986.  
  987. {$S Main}
  988. PROCEDURE DoKeyDown(event: EventRecord);
  989.  
  990. {This is called for any keyDown or autoKey events, except when the
  991.  Command key is held down. It looks at the frontmost window to decide what
  992.  to do with the key typed.}
  993.  
  994. VAR
  995.     window    : WindowPtr;
  996.     key        : CHAR;
  997.     te        : TEHandle;
  998.  
  999. BEGIN
  1000.     window := FrontWindow;
  1001.     IF IsAppWindow(window) THEN BEGIN
  1002.         te := DocumentPeek(window)^.docTE;
  1003.         key := CHR(BAnd(event.message, charCodeMask));
  1004.         IF (key = CHR(kDelChar)) |                            {don't count deletes}
  1005.             (te^^.teLength - (te^^.selEnd - te^^.selStart)
  1006.                             + 1 < kMaxTELength) THEN BEGIN    {but check haven't gone past}
  1007.             TEKey(key, te);
  1008.             AdjustScrollbars(window, FALSE);
  1009.             AdjustTE(window);
  1010.         END ELSE
  1011.             AlertUser(eExceedChar);
  1012.     END;
  1013. END; {DoKeyDown}
  1014.  
  1015.  
  1016. {$S Main}
  1017. PROCEDURE DoContentClick(window: WindowPtr; event: EventRecord);
  1018.  
  1019. {Called when a mouseDown occurs in the content of a window.}
  1020.  
  1021. VAR
  1022.     mouse        : Point;
  1023.     control        : ControlHandle;
  1024.     part, value    : INTEGER;
  1025.     shiftDown    : BOOLEAN;
  1026.     teRect        : Rect;
  1027.  
  1028. BEGIN
  1029.     IF IsAppWindow(window) THEN BEGIN
  1030.         SetPort(window);
  1031.         mouse := event.where;                                            {get the click position}
  1032.         GlobalToLocal(mouse);                                            {convert to local coordinates}
  1033.         {see if we are in the viewRect. if so, we won’t check the controls}
  1034.         GetTERect(window, teRect);
  1035.         IF PtInRect(mouse, teRect) THEN BEGIN
  1036.             shiftDown := BAnd(event.modifiers, shiftKey) <> 0;    {extend if Shift is down}
  1037.             TEClick(mouse, shiftDown, DocumentPeek(window)^.docTE);
  1038.         END ELSE BEGIN
  1039.             part := FindControl(mouse, window, control);
  1040.             WITH DocumentPeek(window)^ DO
  1041.                 CASE part OF
  1042.                     0:;                                            {do nothing for viewRect case}
  1043.                     inThumb: BEGIN
  1044.                         value := GetCtlValue(control);
  1045.                         part := TrackControl(control, mouse, NIL);
  1046.                         IF part <> 0 THEN BEGIN
  1047.                             value := value - GetCtlValue(control);
  1048.                             IF value <> 0 THEN
  1049.                                 IF control = docVScroll THEN
  1050.                                     TEScroll(0, value * docTE^^.lineHeight, docTE)
  1051.                                 ELSE
  1052.                                     TEScroll(value, 0, docTE);
  1053.                         END;
  1054.                     END;
  1055.                     OTHERWISE                                    {must be page or button}
  1056.                         IF control = docVScroll THEN
  1057.                             value := TrackControl(control, mouse, @VActionProc)
  1058.                         ELSE
  1059.                             value := TrackControl(control, mouse, @HActionProc);
  1060.                 END;
  1061.         END;
  1062.     END;
  1063. END; {DoContentClick}
  1064.  
  1065.  
  1066. {$S Main}
  1067. PROCEDURE ResizeWindow(window: WindowPtr);
  1068.  
  1069. {Called when the window has been resized to fix up the controls and content}
  1070.  
  1071. BEGIN
  1072.     WITH window^ DO BEGIN
  1073.         AdjustScrollbars(window, TRUE);
  1074.         AdjustTE(window);
  1075.         InvalRect(portRect);
  1076.     END;
  1077. END; {ResizeWindow}
  1078.  
  1079.  
  1080. {$S Main}
  1081. PROCEDURE GetLocalUpdateRgn(window: WindowPtr; localRgn: RgnHandle);
  1082.  
  1083. {Returns the update region in local coordinates}
  1084.  
  1085. BEGIN
  1086.     CopyRgn(WindowPeek(window)^.updateRgn, localRgn);                        {save old update region}
  1087.     WITH window^.portBits.bounds DO
  1088.         OffsetRgn(localRgn, left, top);                                        {convert to local coords}
  1089. END; {GetLocalUpdateRgn}
  1090.  
  1091.  
  1092. {$S Main}
  1093. PROCEDURE DoGrowWindow(window: WindowPtr; event: EventRecord);
  1094.  
  1095. {Called when a mouseDown occurs in the grow box of an active window. In
  1096.  order to eliminate any 'flicker', we want to invalidate only what is
  1097.  necessary. Since ResizeWindow invalidates the whole portRect, we save
  1098.  the old TE viewRect, intersect it with the new TE viewRect, and
  1099.  remove the result from the update region. However, we must make sure
  1100.  that any old update region that might have been around gets put back.}
  1101.  
  1102. VAR
  1103.     growResult        : LONGINT;
  1104.     tempRect        : Rect;
  1105.     tempRgn            : RgnHandle;
  1106.     ignoreResult    : BOOLEAN;
  1107.  
  1108. BEGIN
  1109.     WITH screenBits.bounds DO
  1110.         SetRect(tempRect, kMinDocDim, kMinDocDim, right, bottom);            {set up limiting values}
  1111.     growResult := GrowWindow(window, event.where, tempRect);
  1112.     IF growResult <> 0 THEN                                                 {see if changed size}
  1113.         WITH DocumentPeek(window)^, window^ DO BEGIN
  1114.             tempRect := docTE^^.viewRect;                                    {save old text box}
  1115.             tempRgn := NewRgn;
  1116.             GetLocalUpdateRgn(window, tempRgn);                                {get localized update region}
  1117.             SizeWindow(window, LoWrd(growResult), HiWrd(growResult), TRUE);
  1118.             ResizeWindow(window);
  1119.             ignoreResult := SectRect(tempRect, docTE^^.viewRect, tempRect);    {find what stayed same}
  1120.             ValidRect(tempRect);                                            {take it out of update}
  1121.             InvalRgn(tempRgn);                                                {put back any prior update}
  1122.             DisposeRgn(tempRgn);
  1123.         END;
  1124. END; {DoGrowWindow}
  1125.  
  1126.  
  1127. {$S Main}
  1128. PROCEDURE DoZoomWindow(window: WindowPtr; part: INTEGER);
  1129.  
  1130. {Called when a mouseClick occurs in the zoom box of an active window.
  1131.  Everything has to get re-drawn here, so we don't mind that
  1132.  ResizeWindow invalidates the whole portRect.}
  1133.  
  1134. BEGIN
  1135.     WITH window^ DO BEGIN
  1136.         EraseRect(portRect);
  1137.         ZoomWindow(window, part, (window = FrontWindow));
  1138.         ResizeWindow(window);
  1139.     END;
  1140. END; {DoZoomWindow}
  1141.  
  1142.  
  1143. {$S Main}
  1144. PROCEDURE DoUpdate(window: WindowPtr);
  1145.  
  1146. {This is called when an update event is received for a window.
  1147.  It calls DrawWindow to draw the contents of an application window,
  1148.  but only if the visRgn is non-empty; for efficiency reasons,
  1149.  not because it is required.}
  1150.  
  1151. BEGIN
  1152.     IF IsAppWindow(window) THEN BEGIN
  1153.         BeginUpdate(window);                    {this sets up the visRgn}
  1154.         IF NOT EmptyRgn(window^.visRgn) THEN    {draw if updating needs to be done}
  1155.             DrawWindow(window);
  1156.         EndUpdate(window);
  1157.     END;
  1158. END; {DoUpdate}
  1159.  
  1160.  
  1161. {$S Main}
  1162. PROCEDURE DoActivate(window: WindowPtr; becomingActive: BOOLEAN);
  1163.  
  1164. {This is called when a window is activated or deactivated.}
  1165.  
  1166. VAR
  1167.     tempRgn, clipRgn    : RgnHandle;
  1168.     growRect            : Rect;
  1169.  
  1170. BEGIN
  1171.     IF IsAppWindow(window) THEN
  1172.         WITH DocumentPeek(window)^ DO
  1173.             IF becomingActive THEN BEGIN
  1174.                 {since we don’t want TEActivate to draw a selection in an area where
  1175.                  we’re going to erase and redraw, we’ll clip out the update region
  1176.                  before calling it.}
  1177.                 tempRgn := NewRgn;
  1178.                 clipRgn := NewRgn;
  1179.                 GetLocalUpdateRgn(window, tempRgn);            {get localized update region}
  1180.                 GetClip(clipRgn);
  1181.                 DiffRgn(clipRgn, tempRgn, tempRgn);            {subtract updateRgn from clipRgn}
  1182.                 SetClip(tempRgn);
  1183.                 TEActivate(docTE);                            {let TE do its thing}
  1184.                 SetClip(clipRgn);                            {restore the full-blown clipRgn}
  1185.                 DisposeRgn(tempRgn);
  1186.                 DisposeRgn(clipRgn);
  1187.  
  1188.                 {the controls need to be redrawn on activation:}
  1189.                 docVScroll^^.contrlVis := kControlVisible;
  1190.                 docHScroll^^.contrlVis := kControlVisible;
  1191.                 InvalRect(docVScroll^^.contrlRect);
  1192.                 InvalRect(docHScroll^^.contrlRect);
  1193.                 {the growbox needs to be redrawn on activation:}
  1194.                 growRect := window^.portRect;
  1195.                 WITH growRect DO BEGIN
  1196.                     top := bottom - kScrollbarAdjust;        {adjust for the scrollbars}
  1197.                     left := right - kScrollbarAdjust;
  1198.                     END;
  1199.                 InvalRect(growRect);
  1200.             END ELSE BEGIN
  1201.                 TEDeactivate(docTE);
  1202.                 {the controls should be hidden immediately on deactivation:}
  1203.                 HideControl(docVScroll);
  1204.                 HideControl(docHScroll);
  1205.                 {the growbox should be changed immediately on deactivation:}
  1206.                 DrawGrowIcon(window);
  1207.             END;
  1208. END; {DoActivate}
  1209.  
  1210.  
  1211. {$S Main}
  1212. PROCEDURE AdjustCursor(mouse: Point; region: RgnHandle);
  1213.  
  1214. {Change the cursor's shape, depending on its position. This also calculates a region
  1215.  that includes the cursor for WaitNextEvent.}
  1216.  
  1217. VAR
  1218.     window        : WindowPtr;
  1219.     arrowRgn    : RgnHandle;
  1220.     iBeamRgn    : RgnHandle;
  1221.     iBeamRect    : Rect;
  1222.  
  1223. BEGIN
  1224.     window := FrontWindow;    {we only adjust the cursor when we are in front}
  1225.     IF (NOT gInBackground) AND (NOT IsDAWindow(window)) THEN BEGIN
  1226.         {calculate regions for different cursor shapes}
  1227.         arrowRgn := NewRgn;
  1228.         iBeamRgn := NewRgn;
  1229.  
  1230.         {start with a big, big rectangular region}
  1231.         SetRectRgn(arrowRgn, kExtremeNeg, kExtremeNeg, kExtremePos, kExtremePos);
  1232.  
  1233.         {calculate iBeamRgn}
  1234.         IF IsAppWindow(window) THEN BEGIN
  1235.             iBeamRect := DocumentPeek(window)^.docTE^^.viewRect;
  1236.             SetPort(window);                    {make a global version of the viewRect}
  1237.             WITH iBeamRect DO BEGIN
  1238.                 LocalToGlobal(topLeft);
  1239.                 LocalToGlobal(botRight);
  1240.             END;
  1241.             RectRgn(iBeamRgn, iBeamRect);
  1242.             WITH window^.portBits.bounds DO
  1243.                 SetOrigin(-left, -top);
  1244.             SectRgn(iBeamRgn, window^.visRgn, iBeamRgn);
  1245.             SetOrigin(0, 0);
  1246.         END;
  1247.  
  1248.         {subtract other regions from arrowRgn}
  1249.         DiffRgn(arrowRgn, iBeamRgn, arrowRgn);
  1250.         
  1251.         {change the cursor and the region parameter}
  1252.         IF PtInRgn(mouse, iBeamRgn) THEN BEGIN
  1253.             SetCursor(GetCursor(iBeamCursor)^^);
  1254.             CopyRgn(iBeamRgn, region);
  1255.         END ELSE BEGIN
  1256.             SetCursor(arrow);
  1257.             CopyRgn(arrowRgn, region);
  1258.         END;
  1259.  
  1260.         {get rid of our local regions}
  1261.         DisposeRgn(arrowRgn);
  1262.         DisposeRgn(iBeamRgn);
  1263.     END;
  1264. END; {AdjustCursor}
  1265.  
  1266.  
  1267. {$S Main}
  1268. PROCEDURE DoEvent(event: EventRecord);
  1269.  
  1270. {Do the right thing for an event. Determine what kind of event it is, and call
  1271.  the appropriate routines.}
  1272.  
  1273. VAR
  1274.     part    : INTEGER;
  1275.     window    : WindowPtr;
  1276.     key        : CHAR;
  1277.     ignore    : BOOLEAN;
  1278.  
  1279. BEGIN
  1280.     CASE event.what OF
  1281.         nullEvent:
  1282.             DoIdle;
  1283.         mouseDown: BEGIN
  1284.             part := FindWindow(event.where, window);
  1285.             CASE part OF
  1286.                 inMenuBar: BEGIN
  1287.                     AdjustMenus;
  1288.                     DoMenuCommand(MenuSelect(event.where));
  1289.                 END;
  1290.                 inSysWindow:
  1291.                     SystemClick(event, window);
  1292.                 inContent:
  1293.                     IF window <> FrontWindow THEN BEGIN
  1294.                         SelectWindow(window);
  1295.                         {DoEvent(event);}    {use this line for "do first click"}
  1296.                     END ELSE
  1297.                         DoContentClick(window, event);
  1298.                 inDrag:
  1299.                     DragWindow(window, event.where, screenBits.bounds);
  1300.                 inGrow:
  1301.                     DoGrowWindow(window, event);
  1302.                 inGoAway:
  1303.                     IF TrackGoAway(window, event.where) THEN
  1304.                         ignore := DoCloseWindow(window);        {we don't care if cancelled}
  1305.                 inZoomIn, inZoomOut:
  1306.                     IF TrackBox(window, event.where, part) THEN
  1307.                         DoZoomWindow(window, part);
  1308.             END;
  1309.         END;
  1310.         keyDown, autoKey: BEGIN
  1311.             key := CHR(BAnd(event.message, charCodeMask));
  1312.             IF BAnd(event.modifiers, cmdKey) <> 0 THEN BEGIN    {Command key down}
  1313.                 IF event.what = keyDown THEN BEGIN
  1314.                     AdjustMenus;            {enable/disable/check menu items properly}
  1315.                     DoMenuCommand(MenuKey(key));
  1316.                 END;
  1317.             END ELSE
  1318.                 DoKeyDown(event);
  1319.         END;                                {call DoActivate with the window and...}
  1320.         activateEvt:                        {TRUE for activate, FALSE for deactivate}
  1321.             DoActivate(WindowPtr(event.message), BAnd(event.modifiers, activeFlag) <> 0);
  1322.         updateEvt:                            {call DoUpdate with the window to update}
  1323.             DoUpdate(WindowPtr(event.message));
  1324.         kOSEvent:
  1325.             CASE BAnd(BRotL(event.message, 8), $FF) OF    {high byte of message}
  1326.                 kMouseMovedMessage:
  1327.                     DoIdle;                    {mouse moved is also an idle event}
  1328.                 kSuspendResumeMessage: BEGIN
  1329.                     gInBackground := BAnd(event.message, kResumeMask) = 0;
  1330.                     DoActivate(FrontWindow, NOT gInBackground);
  1331.                 END;
  1332.             END;
  1333.     END;
  1334. END; {DoEvent}
  1335.  
  1336.  
  1337. {$S Main}
  1338. PROCEDURE EventLoop;
  1339.  
  1340. {Get events forever, and handle them by calling DoEvent.
  1341.  Also call AdjustCursor each time through the loop.}
  1342.  
  1343. VAR
  1344.     cursorRgn        : RgnHandle;
  1345.     gotEvent        : BOOLEAN;
  1346.     event            : EventRecord;
  1347.  
  1348. BEGIN
  1349.     cursorRgn := NewRgn;        {we'll pass an empty region to WNE the first time thru}
  1350.     REPEAT
  1351.         IF gHasWaitNextEvent THEN
  1352.             gotEvent := WaitNextEvent(everyEvent, event, GetSleep, cursorRgn)
  1353.         ELSE BEGIN
  1354.             SystemTask;
  1355.             gotEvent := GetNextEvent(everyEvent, event);
  1356.         END;
  1357.         IF gotEvent THEN BEGIN
  1358.             AdjustCursor(event.where, cursorRgn);
  1359.             DoEvent(event);
  1360.             END
  1361.         ELSE
  1362.             DoIdle;
  1363.         AdjustCursor(event.where, cursorRgn);
  1364.     UNTIL FALSE;    {loop forever}
  1365. END; {EventLoop}
  1366.  
  1367.  
  1368. PROCEDURE _DataInit; EXTERNAL;
  1369.  
  1370. {This routine is automatically linked in by the MPW Linker. This external
  1371.  reference to it is done so that we can unload its segment, %A5Init.}
  1372.  
  1373.  
  1374. {$S Main}
  1375. BEGIN
  1376.     UnloadSeg(@_DataInit);    {note that _DataInit must not be in Main!}
  1377.     
  1378.     {1.01 - call to ForceEnvirons removed}
  1379.     {If you have stack requirements that differ from the default,
  1380.      then you could use SetApplLimit to increase StackSpace at 
  1381.      this point, before calling MaxApplZone.}
  1382.      
  1383.     MaxApplZone;            {expand the heap so code segments load at the top}
  1384.  
  1385.     Initialize;                {initialize the program}
  1386.     UnloadSeg(@Initialize);    {note that Initialize must not be in Main!}
  1387.  
  1388.     EventLoop;                {call the main event loop}
  1389. END.
  1390.